<!--
  - 遍历帧长累积短时傅里叶变换 eflaSTFT
  - Ergodic Frame Length Accumulation Short-Time Fourier Transform
 -->
 
<!-- 视图层 --><!-- 页头(上) -->
<template><header-comp />

<!-- 功能简介 -->
<uni-card title="功能简介">

   <!-- 文字 -->
   <view><text>
   　　遍历帧长累积短时傅里叶变换（Ergodic Frame Length Accumulation Short-Time Fourier Transform, eflaSTFT）。\n
   　　用的TF.js的STFT的API，遍历frameLength传参，把得到的矩阵的频率轴积分。\n
   　　把一维光谱给二维化的实现，挺没意思的，即不懂谱学，又不懂卷积的人水文章用的。
   </text></view>

</uni-card>

<!-- TFjs初始化组件 -->
<tfjsInit />

<!-- 操作 -->
<uni-card title="操作">
 <!-- 通告栏 -->
 <uni-notice-bar text=
   "相关参数细节看代码逻辑层。
   一定注意监测内存使用！"
 />

 <!-- 训练按钮容器及按钮 -->
 <view class="center"><button
   :disabled="!tfBackendRef"
   @click="testMain()"
   size="mini" type="primary"
 >
   运行代码
 </button></view>

 <!-- 打开图表板容器及按钮 -->
 <view class="center"><button
   @click="showVisor()"
   size="mini" type="primary"
 >
   打开图表板
 </button></view>

</uni-card>

<!-- 页脚(下) -->
<footer-comp /></template>

<!-- 逻辑层 -->
<script setup>
// 导入vue的onMounted、ref方法
// import { shallowRef, ref } from "vue"
// 导入tfjs-visor库
import * as tf from "@tensorflow/tfjs"
import * as tfvis from "@tensorflow/tfjs-vis"
// 导入TF初始化组件
import tfjsInit from "../index/tfjs-init.vue"
// 导入xlsx库
// import * as XLSX from "xlsx"
// 导入公共方法库的方法
import {
  // TF初始化的确认flag
  tfBackendRef,
  // 打开图表板
  showVisor,
  // AOA数组生成线形图
  aoaTotfvisChart,
  // 数据归一化方法
  minToZero, maxToOne,
} from "../../scripts/app_common.js"
// 导入测试数据集
import { testData } from "../../datasets/test-data.js"


/**
 * @ref对象
 */


/**
 * @其他对象
 */

/**
 * @主方法
 * 实际执行的方法
 */
function testMain() {

  // 记个时
  console.time("eflaSTFT处理耗时")
  console.log("开始对数据进行eflaSTFT处理...")
  
  /**
   * @数据预处理
   */
  // 归一化
  minToZero(testData.y, true)
  maxToOne(testData.y)
  
  // 数据先做个图
  aoaTotfvisChart(
    // 图表类型
    "linechart",
    // 图表容器
    { tab: "数据", name: "原始谱图" },
    // 图表数据：第一个数据
    [testData],
    // 数据序列标签
    ["数据"]
  )

  /**
   * @eflaSTFT
   * 遍历处理
   */
  // 接收结果张量，y轴标签数组
  const { dataTensor, yLabelsArr } = tf.tidy(() => {
    
    // 一些参数的设定
    // 最小frameLength，窗口起始值
    const minFrameLength = 3
    // 最大frameLength，窗口最大值
    const maxFrameLength = testData.y.length
    // 窗口长度的遍历步长
    const frameLengthStep = 16
    // FFT的步长
    const fftFrameStep = 1
    // FFT的计算选点长度，默认为null，自动选择
    const fftLength = null
    // 窗口函数，默认为汉宁窗
    const windowFunction = tf.signal.hannWindow
    
    // 数据数组
    const tensorArr = []
    // y轴标签数组
    const yLabelsArr = []
    // 遍历操作
    for (let frameLength = minFrameLength; frameLength < maxFrameLength; frameLength += frameLengthStep) {
      
      // y轴标签添加进数组里，便于作图
      yLabelsArr.push(frameLength)
      
      /**
       * @STFT操作
       * 生成的数据压缩为一维张量输出
       */
      // 接受返回的单个窗的一维张量
      const uniDataTensor = tf.tidy(() => {
        
        /**
         * @边缘处理
         * 往数据数组左右补0，确保输出数组的长度和输入数据一致
         */
        // 深拷贝testData对象的y
        const yDeepCopy = JSON.parse(JSON.stringify(testData.y))
        // 边缘填充数，向下取整
        const padding = Math.floor(frameLength / 2)
        // 建立边缘填充数组，用0填充
        const paddingArr = new Array(padding).fill(0)
        // 在yDeepCopy的头部和尾部分别添加paddingArr
        yDeepCopy.unshift(...paddingArr)
        yDeepCopy.push(...paddingArr)
        // 用处理好的数组建立一维张量
        const dataTensor = tf.tensor1d(yDeepCopy)
        
        /**
         * @STFT
         * 短时傅立叶变换，然后合并实部虚部取绝对值
         */
        const stftTensor = tf.signal.stft(
          // 数据
          dataTensor,
          // 窗口大小
          frameLength,
          // 步长，永远是1
          fftFrameStep,
          // FFT长度
          fftLength,
          // 窗口函数
          windowFunction
        ).abs()
        
        /**
         * @积分求和降维
         */
        // 沿着内轴求和，缩成一维张量，即为uniDataTensor
        const uniDataTensor = stftTensor.sum(1)
        // 归一化
        const tensorMin = uniDataTensor.min()
        const tensorMax = uniDataTensor.max()
        const normalization = uniDataTensor.sub(tensorMin)
          .div(tensorMax.sub(tensorMin))
        
        // 返回结果
        return normalization

      })
      // 将uniDataTensor添加到tensorArr数组中
      tensorArr.push(uniDataTensor)
    }

    // 组合这些张量，然后转置
    const dataTensor = tf.stack(tensorArr).transpose()

    // 返回dataTensor和yLabelsArr
    return {
      dataTensor: dataTensor,
      yLabelsArr: yLabelsArr
    }

  })

  // 输出耗时
  console.timeEnd("eflaSTFT处理耗时")

  // 打印张量
  dataTensor.print(true)

  // 作图
  tfvis.render.heatmap(
    // 图表容器
    { tab: "数据", name: "STFT谱图" },
    // 数据
    { values: dataTensor, yTickLabels: yLabelsArr }
  )

}


</script>
